/**
 * 3D position
 */

import { addComponent, defineComponent, hasComponent, Types, type IWorld, defineQuery, type Entity, removeComponent, type Component } from './ecs';
import { isLandscape } from './landscape';
import { describe } from '$lib';

const Pos3 = defineComponent('Pos3', {
  x: Types.i32,
  y: Types.i32,
  z: Types.i32,
});

export const hasPos3 = (world: IWorld, e: Entity) => {
  return hasComponent(world, Pos3, e);
};

const pos3Query = defineQuery([Pos3]);

export const afterLoad = (world: IWorld) => {
  for (let e of pos3Query(world)) {
    const {x, y, z} = getPos3(e);
    registerOnGrid(world, e, x, y, z);
  }
};

export const getPos3 = (e) => {
  return {
    x: Pos3.x[e],
    y: Pos3.y[e],
    z: Pos3.z[e],
  };
};

const grid = new Map();

export const placeOnMap = (world: IWorld, e: Entity, x, y, z) => {
  addComponent(world, Pos3, e);
  moveTo(world, e, x, y, z);
  processLevelTriggers(world, e, undefined, z);
};

export const removeFromMap = (world, e) => {
  // const {x, y, z} = getPos3(e);
  // removeFrom(world, e, x, y);
  removeComponent(world, Pos3, e);
  /// TODO
};

export const moveTo = (world, e, x, y, z) => {
  Pos3.x[e] = x;
  Pos3.y[e] = y;
  Pos3.z[e] = z;
  registerOnGrid(world, e, x, y, z);
  processLocationTriggers(world, e, x, y, z);
};

const levelTriggers = [];

export const registerLevelTrigger = (component, trigger) => {
  levelTriggers.push([component, trigger]);
};

export const processLevelTriggers = (world, e, z0, z1) => {
  for (let [component, trigger] of levelTriggers) {
    if (hasComponent(world, component, e)) {
      trigger(world, e, z0, z1);
    }
  }
};

const registerOnGrid = (world, e, x, y, z) => {
  getStack(x, y, z).push(e);
  // Make modular?
  if (isLandscape(world, e)) {
    registerLandscape(world, e);
  }
};

export const registerLandscape = (world, e) => {
  const {x, y, z} = getPos3(e);
  getTile(x, y, z).landscape = e;
};

const getTile = (x, y, z) => {
  let plane = grid.get(z);
  if (plane === undefined) {
    plane = new Map();
    grid.set(z, plane);
  }
  let line = plane.get(y);
  if (line === undefined) {
    line = new Map();
    plane.set(y, line);
  }
  let tile = line.get(x);
  if (tile === undefined) {
    tile = {
      stack: [],
      landscape: undefined,
    };
    line.set(x, tile);
  }
  return line.get(x);
};

const getStack = (x, y, z) => {
  return getTile(x, y, z).stack;
};

export const onTile = (world: IWorld, x: number, y: number, z: number) => getStack(x, y, z);

export const move = (world, e, x, y, z) => {
  const z0 = Pos3.z[e];
  removeFrom(world, e, Pos3.x[e], Pos3.y[e], Pos3.z[e]);
  moveTo(world, e, x, y, z);
  if (z0 !== z)
    processLevelTriggers(world, e, z0, z);
};

export const removeFrom = (world, e, x, y, z) => {
  // debug-only
  if (!hasComponent(world, Pos3, e)) {
    console.error("reoveFrom when no Pos3");
    return;
  }
  const stack = getStack(x, y, z);
  const ix = stack.indexOf(e);
  if (ix === -1) {
    console.warn(`Trying to remove entity from grid cell where it does not belong: ${describe(world, e)} ${x} ${y} ${z}`);
    return;
  }
  stack.splice(ix, 1);
};

export const unregisterFromMap = (world, e) => {
  const {x, y, z} = getPos3(e);
  removeFrom(world, e, x, y, z);
  processLevelTriggers(world, e, z, undefined);
};

/**
 * Get map piece around entitry max dist of r
 */
export const getGridAt = (world, e, r) => {
  const {x, y, z} = getPos3(e);
  const res = new Map();
  for (let dy = -r; dy <= r; ++dy) {
    const line = new Map();
    res.set(dy, line);
    for (let dx = -r; dx <= r; ++dx) {
      line.set(dx, {
        stack: getStack(x+dx, y+dy, z)
      });
    }
  }
  return res;
};

/**
 * Mind boundary conditions!
 */
export const getGrid = (world, z, x0, y0, x1, y1) => {
  const res = new Map();
  for (let y = y0; y <= y1; ++y) {
    const line = new Map();
    res.set(y, line);
    for (let x = x0; x <= x1; ++x) {
      line.set(x, {
        stack: getStack(x, y, z)
      });
    }
  }
  return res;
};

export const landscapeAt = (world, x: number, y: number, z: number) => {
  return getTile(x, y, z).landscape;
};

type LocationTrigger = {
  target: Component,
  trigger: (world: IWorld, feature: Entity, e: Entity) => void,
};

const locationTriggers: LocationTrigger[] = [];

export const registerLocationTrigger = (trigger: LocationTrigger) => {
  locationTriggers.push(trigger);
};

const processLocationTriggers = (world: IWorld, e: Entity, x: number, y: number, z: number) => {
  const stack = getStack(x, y, z);
  for (let other of stack) {
    if (e === other)
      continue;
    for (let {target, trigger} of locationTriggers) {
      if (hasComponent(world, target, other)) {
        trigger(world, other, e);
      }
    }
  }
};
